using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using RTMP.SimpleClient.Exceptions;

namespace RTMP.SimpleClient.Helpers
{
    /// <summary>
    /// AMF0 编码/解码助手类
    /// </summary>
    public static class AMF0Helper
    {
        /// <summary>
        /// AMF0 数据类型枚举
        /// </summary>
        public enum AMF0Type : byte
        {
            Number = 0x00,
            Boolean = 0x01,
            String = 0x02,
            Object = 0x03,
            MovieClip = 0x04,
            Null = 0x05,
            Undefined = 0x06,
            Reference = 0x07,
            ECMAArray = 0x08,
            ObjectEnd = 0x09,
            StrictArray = 0x0A,
            Date = 0x0B,
            LongString = 0x0C,
            Unsupported = 0x0D,
            RecordSet = 0x0E,
            XMLDocument = 0x0F,
            TypedObject = 0x10,
            AMF3Data = 0x11
        }

        /// <summary>
        /// 编码 AMF0 值
        /// </summary>
        /// <param name="value">要编码的值</param>
        /// <returns>编码后的字节数组</returns>
        public static byte[] Encode(object? value)
        {
            using (var ms = new MemoryStream())
            using (var writer = new BinaryWriter(ms))
            {
                EncodeValue(writer, value);
                return ms.ToArray();
            }
        }

        /// <summary>
        /// 编码 AMF0 值到 Span
        /// </summary>
        /// <param name="buffer">输出缓冲区</param>
        /// <param name="value">要编码的值</param>
        /// <returns>写入的字节数</returns>
        public static int Encode(Span<byte> buffer, object? value)
        {
            return EncodeValueToSpan(buffer, value);
        }

        /// <summary>
        /// 编码无类型前缀的值（用于消息头编码）
        /// </summary>
        /// <param name="value">要编码的值</param>
        /// <returns>编码后的字节数组</returns>
        public static byte[] EncodeWithoutPrefix(uint value)
        {
            using (var ms = new MemoryStream())
            using (var writer = new BinaryWriter(ms))
            {
                WriteBigEndianUInt32(writer, value);
                return ms.ToArray();
            }
        }

        /// <summary>
        /// 编码无类型前缀的值到 Span（用于消息头编码）
        /// </summary>
        /// <param name="buffer">输出缓冲区</param>
        /// <param name="value">要编码的值</param>
        /// <returns>写入的字节数</returns>
        public static int EncodeWithoutPrefix(Span<byte> buffer, uint value)
        {
            return WriteBigEndianUInt32ToSpan(buffer, value);
        }

        /// <summary>
        /// 解码 AMF0 值
        /// </summary>
        /// <param name="data">要解码的数据</param>
        /// <returns>解码后的对象</returns>
        public static object? Decode(byte[] data)
        {
            using (var ms = new MemoryStream(data))
            using (var reader = new BinaryReader(ms))
            {
                return DecodeValue(reader);
            }
        }

        /// <summary>
        /// 编码单个值
        /// </summary>
        private static void EncodeValue(BinaryWriter writer, object? value)
        {
            if (value == null)
            {
                writer.Write((byte)AMF0Type.Null);
                return;
            }

            switch (value)
            {
                case double d:
                    writer.Write((byte)AMF0Type.Number);
                    WriteBigEndianDouble(writer, d);
                    break;

                case int i:
                    writer.Write((byte)AMF0Type.Number);
                    WriteBigEndianDouble(writer, (double)i);
                    break;

                case uint u:
                    writer.Write((byte)AMF0Type.Number);
                    WriteBigEndianDouble(writer, (double)u);
                    break;

                case bool b:
                    writer.Write((byte)AMF0Type.Boolean);
                    writer.Write(b ? (byte)1 : (byte)0);
                    break;

                case string s:
                    EncodeString(writer, s);
                    break;

                case Dictionary<string, object> dict:
                    EncodeObject(writer, dict);
                    break;

                case List<object> list:
                    EncodeArray(writer, list);
                    break;

                default:
                    // 对于不支持的类型，编码为 null
                    writer.Write((byte)AMF0Type.Null);
                    break;
            }
        }

        /// <summary>
        /// 编码单个值到 Span
        /// </summary>
        private static int EncodeValueToSpan(Span<byte> buffer, object? value)
        {
            int offset = 0;

            if (value == null)
            {
                if (buffer.Length < 1) throw new ArgumentException("Buffer too small");
                buffer[offset++] = (byte)AMF0Type.Null;
                return offset;
            }

            switch (value)
            {
                case double d:
                    if (buffer.Length < offset + 9) throw new ArgumentException("Buffer too small");
                    buffer[offset++] = (byte)AMF0Type.Number;
                    offset += WriteBigEndianDoubleToSpan(buffer.Slice(offset), d);
                    break;

                case int i:
                    if (buffer.Length < offset + 9) throw new ArgumentException("Buffer too small");
                    buffer[offset++] = (byte)AMF0Type.Number;
                    offset += WriteBigEndianDoubleToSpan(buffer.Slice(offset), (double)i);
                    break;

                case uint u:
                    if (buffer.Length < offset + 9) throw new ArgumentException("Buffer too small");
                    buffer[offset++] = (byte)AMF0Type.Number;
                    offset += WriteBigEndianDoubleToSpan(buffer.Slice(offset), (double)u);
                    break;

                case bool b:
                    if (buffer.Length < offset + 2) throw new ArgumentException("Buffer too small");
                    buffer[offset++] = (byte)AMF0Type.Boolean;
                    buffer[offset++] = b ? (byte)1 : (byte)0;
                    break;

                case string s:
                    offset += EncodeString(buffer.Slice(offset), s);
                    break;

                case Dictionary<string, object> dict:
                    offset += EncodeObject(buffer.Slice(offset), dict);
                    break;

                case List<object> list:
                    offset += EncodeArray(buffer.Slice(offset), list);
                    break;

                default:
                    // 对于不支持的类型，编码为 null
                    if (buffer.Length < 1) throw new ArgumentException("Buffer too small");
                    buffer[offset++] = (byte)AMF0Type.Null;
                    break;
            }

            return offset;
        }

        /// <summary>
        /// 编码字符串
        /// </summary>
        private static void EncodeString(BinaryWriter writer, string value)
        {
            var bytes = Encoding.UTF8.GetBytes(value ?? string.Empty);

            if (bytes.Length > ushort.MaxValue)
            {
                writer.Write((byte)AMF0Type.LongString);
                writer.Write((uint)bytes.Length);
            }
            else
            {
                writer.Write((byte)AMF0Type.String);
                writer.Write((ushort)bytes.Length);
            }

            writer.Write(bytes);
        }

        /// <summary>
        /// 编码对象
        /// </summary>
        private static void EncodeObject(BinaryWriter writer, Dictionary<string, object> value)
        {
            writer.Write((byte)AMF0Type.Object);

            foreach (var kvp in value)
            {
                var keyBytes = Encoding.UTF8.GetBytes(kvp.Key);
                writer.Write((ushort)keyBytes.Length);
                writer.Write(keyBytes);
                EncodeValue(writer, kvp.Value);
            }

            writer.Write((ushort)0); // 空字符串表示对象结束
            writer.Write((byte)AMF0Type.ObjectEnd);
        }

        /// <summary>
        /// 编码数组
        /// </summary>
        private static void EncodeArray(BinaryWriter writer, List<object> value)
        {
            writer.Write((byte)AMF0Type.StrictArray);
            writer.Write((uint)value.Count);

            foreach (var item in value)
            {
                EncodeValue(writer, item);
            }
        }

        /// <summary>
        /// 编码字符串到 Span
        /// </summary>
        private static int EncodeString(Span<byte> buffer, string value)
        {
            int offset = 0;
            var bytes = Encoding.UTF8.GetBytes(value ?? string.Empty);

            if (bytes.Length > ushort.MaxValue)
            {
                if (buffer.Length < offset + 5) throw new ArgumentException("Buffer too small");
                buffer[offset++] = (byte)AMF0Type.LongString;
                offset += WriteBigEndianUInt32ToSpan(buffer.Slice(offset), (uint)bytes.Length);
            }
            else
            {
                if (buffer.Length < offset + 3) throw new ArgumentException("Buffer too small");
                buffer[offset++] = (byte)AMF0Type.String;
                offset += WriteBigEndianUInt16ToSpan(buffer.Slice(offset), (ushort)bytes.Length);
            }

            if (buffer.Length < offset + bytes.Length) throw new ArgumentException("Buffer too small");
            bytes.CopyTo(buffer.Slice(offset));
            offset += bytes.Length;
            return offset;
        }

        /// <summary>
        /// 编码对象到 Span
        /// </summary>
        private static int EncodeObject(Span<byte> buffer, Dictionary<string, object> value)
        {
            int offset = 0;
            if (buffer.Length < offset + 1) throw new ArgumentException("Buffer too small");
            buffer[offset++] = (byte)AMF0Type.Object;

            foreach (var kvp in value)
            {
                var keyBytes = Encoding.UTF8.GetBytes(kvp.Key);
                if (buffer.Length < offset + 2 + keyBytes.Length) throw new ArgumentException("Buffer too small");
                offset += WriteBigEndianUInt16ToSpan(buffer.Slice(offset), (ushort)keyBytes.Length);
                keyBytes.CopyTo(buffer.Slice(offset));
                offset += keyBytes.Length;
                offset += EncodeValueToSpan(buffer.Slice(offset), kvp.Value);
            }

            if (buffer.Length < offset + 3) throw new ArgumentException("Buffer too small");
            offset += WriteBigEndianUInt16ToSpan(buffer.Slice(offset), 0); // 空字符串表示对象结束
            buffer[offset++] = (byte)AMF0Type.ObjectEnd;
            return offset;
        }

        /// <summary>
        /// 编码数组到 Span
        /// </summary>
        private static int EncodeArray(Span<byte> buffer, List<object> value)
        {
            int offset = 0;
            if (buffer.Length < offset + 5) throw new ArgumentException("Buffer too small");
            buffer[offset++] = (byte)AMF0Type.StrictArray;
            offset += WriteBigEndianUInt32ToSpan(buffer.Slice(offset), (uint)value.Count);

            foreach (var item in value)
            {
                offset += EncodeValueToSpan(buffer.Slice(offset), item);
            }

            return offset;
        }

        /// <summary>
        /// 写入大端字节序的double值
        /// </summary>
        private static void WriteBigEndianDouble(BinaryWriter writer, double value)
        {
            var bytes = BitConverter.GetBytes(value);
            if (BitConverter.IsLittleEndian)
            {
                Array.Reverse(bytes);
            }
            writer.Write(bytes);
        }

        /// <summary>
        /// 写入大端字节序的double值到 Span
        /// </summary>
        private static int WriteBigEndianDoubleToSpan(Span<byte> buffer, double value)
        {
            if (buffer.Length < 8) throw new ArgumentException("Buffer too small for double");
            var bytes = BitConverter.GetBytes(value);
            if (BitConverter.IsLittleEndian)
            {
                Array.Reverse(bytes);
            }
            bytes.CopyTo(buffer);
            return 8;
        }

        /// <summary>
        /// 写入大端字节序的uint值
        /// </summary>
        private static void WriteBigEndianUInt32(BinaryWriter writer, uint value)
        {
            var bytes = BitConverter.GetBytes(value);
            if (BitConverter.IsLittleEndian)
            {
                Array.Reverse(bytes);
            }
            writer.Write(bytes);
        }

        /// <summary>
        /// 写入大端字节序的uint值到 Span
        /// </summary>
        private static int WriteBigEndianUInt32ToSpan(Span<byte> buffer, uint value)
        {
            if (buffer.Length < 4) throw new ArgumentException("Buffer too small for uint32");
            var bytes = BitConverter.GetBytes(value);
            if (BitConverter.IsLittleEndian)
            {
                Array.Reverse(bytes);
            }
            bytes.CopyTo(buffer);
            return 4;
        }

        /// <summary>
        /// 写入大端字节序的ushort值到 Span
        /// </summary>
        private static int WriteBigEndianUInt16ToSpan(Span<byte> buffer, ushort value)
        {
            if (buffer.Length < 2) throw new ArgumentException("Buffer too small for uint16");
            var bytes = BitConverter.GetBytes(value);
            if (BitConverter.IsLittleEndian)
            {
                Array.Reverse(bytes);
            }
            bytes.CopyTo(buffer);
            return 2;
        }

        /// <summary>
        /// 解码单个值
        /// </summary>
        private static object? DecodeValue(BinaryReader reader)
        {
            var type = (AMF0Type)reader.ReadByte();

            switch (type)
            {
                case AMF0Type.Null:
                    return null;

                case AMF0Type.Boolean:
                    return reader.ReadByte() != 0;

                case AMF0Type.Number:
                    return ReadBigEndianDouble(reader);

                case AMF0Type.String:
                    return DecodeString(reader, false);

                case AMF0Type.LongString:
                    return DecodeString(reader, true);

                case AMF0Type.Object:
                    return DecodeObject(reader);

                case AMF0Type.StrictArray:
                    return DecodeArray(reader);

                default:
                    throw new RTMPUnsupportedException($"AMF0 type {type} is not supported");
            }
        }

        /// <summary>
        /// 解码字符串
        /// </summary>
        private static string DecodeString(BinaryReader reader, bool isLong)
        {
            int length = isLong ? (int)reader.ReadUInt32() : reader.ReadUInt16();
            var bytes = reader.ReadBytes(length);
            return Encoding.UTF8.GetString(bytes);
        }

        /// <summary>
        /// 解码对象
        /// </summary>
        private static Dictionary<string, object?> DecodeObject(BinaryReader reader)
        {
            var result = new Dictionary<string, object?>();

            while (true)
            {
                var keyLength = reader.ReadUInt16();
                if (keyLength == 0)
                {
                    var endMarker = reader.ReadByte();
                    if (endMarker == (byte)AMF0Type.ObjectEnd)
                    {
                        break;
                    }
                    else
                    {
                        throw new RTMPException("Invalid object end marker");
                    }
                }

                var keyBytes = reader.ReadBytes(keyLength);
                var key = Encoding.UTF8.GetString(keyBytes);
                var value = DecodeValue(reader);
                result[key] = value;
            }

            return result;
        }

        /// <summary>
        /// 解码数组
        /// </summary>
        private static List<object?> DecodeArray(BinaryReader reader)
        {
            var result = new List<object?>();
            var count = reader.ReadUInt32();

            for (int i = 0; i < count; i++)
            {
                result.Add(DecodeValue(reader));
            }

            return result;
        }

        /// <summary>
        /// 读取大端字节序的double值
        /// </summary>
        private static double ReadBigEndianDouble(BinaryReader reader)
        {
            var bytes = reader.ReadBytes(8);
            if (BitConverter.IsLittleEndian)
            {
                Array.Reverse(bytes);
            }
            return BitConverter.ToDouble(bytes, 0);
        }
    }
}
